(function (global, factory) {
    'use strict';
    if (typeof module === 'object' && typeof module.exports === 'object') {
        module.exports = global.document ? factory(global) : function (w) {
            if (!w.document) {
                throw new Error('initNECaptchaWithFallback requires a window with a document');
            }
            return factory(w);
        };
    } else if (typeof define === 'function' && define.amd) {
        define('initNECaptchaWithFallback', [], function () {
            return factory(global);
        });
    } else {
        global.initNECaptchaWithFallback = factory(global);
    }
}(typeof window !== 'undefined' ? window : this, function (window) {
    'use strict';
    var errorCallbackCount = 0;

    // 常量
    var DEFAULT_VALIDATE = 'QjGAuvoHrcpuxlbw7cp4WnIbbjzG4rtSlpc7EDovNHQS._ujzPZpeCInSxIT4WunuDDh8dRZYF2GbBGWyHlC6q5uEi9x-TXT9j7J705vSsBXyTar7aqFYyUltKYJ7f4Y2TXm_1Mn6HFkb4M7URQ_rWtpxQ5D6hCgNJYC0HpRE7.2sttqYKLoi7yP1KHzK-PptdHHkVwb77cwS2EJW7Mj_PsOtnPBubTmTZLpnRECJR99dWTVC11xYG0sx8dJNLUxUFxEyzTfX4nSmQz_T5sXATRKHtVAz7nmV0De5unmflfAlUwMGKlCT1khBtewlgN5nHvyxeD8Z1_fPVzi9oznl-sbegj6lKfCWezmLcwft8.4yaVh6SlzXJq-FnSK.euq9OBd5jYc82ge2_hEca1fGU--SkPRzgwkzew4O4qjdS2utdPwFONnhKAIMJRPUmCV4lPHG1OeRDvyNV8sCnuFMw7leasxIhPoycl4pm5bNy70Z1laozEGJgItVNr3'; // 默认validate
    var FALLBACK_LANG = {
        'zh-CN': '前方拥堵，已自动跳过验证',
        'en': 'captcha error，Verified automatically'
    };
    var CACHE_MIN = 1000 * 60; // 缓存时长单位，1分钟

    // 工具函数
    function loadScript(src, cb) {
        var head = document.head || document.getElementsByTagName('head')[0];
        var script = document.createElement('script');

        cb = cb || function () { };

        script.type = 'text/javascript';
        script.charset = 'utf8';
        script.async = true;
        script.src = src;

        if (!('onload' in script)) {
            script.onreadystatechange = function () {
                if (this.readyState !== 'complete' && this.readyState !== 'loaded') return;
                this.onreadystatechange = null;
                cb(null, script); // there is no way to catch loading errors in IE8
            };
        }

        script.onload = function () {
            this.onerror = this.onload = null;
            cb(null, script);
        };
        script.onerror = function () {
            // because even IE9 works not like others
            this.onerror = this.onload = null;
            cb(new Error('Failed to load ' + this.src), script);
        };

        head.appendChild(script);
    }

    function joinUrl(protocol, host, path) {
        protocol = protocol || '';
        host = host || '';
        path = path || '';
        if (protocol) {
            protocol = protocol.replace(/:?\/{0,2}$/, '://');
        }
        if (host) {
            var matched = host.match(/^([-0-9a-zA-Z.:]*)(\/.*)?/);
            host = matched[1];
            path = (matched[2] || '') + '/' + path;
        }
        !host && (protocol = '');

        return protocol + host + path;
    }

    function setDomText(el, value) {
        if (value === undefined) return;
        var nodeType = el.nodeType;
        if (nodeType === 1 || nodeType === 11 || nodeType === 9) {
            if (typeof el.textContent === 'string') {
                el.textContent = value;
            } else {
                el.innerText = value;
            }
        }
    }

    function queryAllByClassName(selector, node) {
        node = node || document;
        if (node.querySelectorAll) {
            return node.querySelectorAll(selector);
        }
        if (!/^\.[^.]+$/.test(selector)) return [];
        if (node.getElementsByClassName) {
            return node.getElementsByClassName(selector);
        }

        var children = node.getElementsByTagName('*');
        var current;
        var result = [];
        var className = selector.slice(1);
        for (var i = 0, l = children.length; i < l; i++) {
            current = children[i];
            if (~(' ' + current.className + ' ').indexOf(' ' + className + ' ')) {
                result.push(current);
            }
        }
        return result;
    }

    function assert(condition, msg) {
        if (!condition) throw new Error('[NECaptcha] ' + msg);
    }

    function isInteger(val) {
        if (Number.isInteger) {
            return Number.isInteger(val);
        }
        return typeof val === 'number' && isFinite(val) && Math.floor(val) === val;
    }

    function isArray(val) {
        if (Array.isArray) return Array.isArray(val);
        return Object.prototype.toString.call(val) === '[object Array]';
    }

    function ObjectAssign() {
        if (Object.assign) {
            return Object.assign.apply(null, arguments);
        }

        var target = {};
        for (var index = 1; index < arguments.length; index++) {
            var source = arguments[index];
            if (source != null) {
                for (var key in source) {
                    if (Object.prototype.hasOwnProperty.call(source, key)) {
                        target[key] = source[key];
                    }
                }
            }
        }
        return target;
    }

    function getTimestamp(msec) {
        msec = !msec && msec !== 0 ? msec : 1;
        return parseInt((new Date()).valueOf() / msec, 10);
    }

    // 降级方案
    function normalizeFallbackConfig(customConfig) {
        var siteProtocol = window.location.protocol.replace(':', '');
        var defaultConf = {
            protocol: siteProtocol === 'http' ? 'http' : 'https',
            lang: 'zh-CN',
            errorFallbackCount: 3
        };
        var config = ObjectAssign({}, defaultConf, customConfig);

        var errorFallbackCount = config.errorFallbackCount;
        assert(
            errorFallbackCount === undefined || (isInteger(errorFallbackCount) && errorFallbackCount >= 1),
            'errorFallbackCount must be an integer, and it\'s value greater than or equal one');

        return config;
    }

    function loadResource(config, cb) {
        if (window.initNECaptcha) return cb(null);
        function genUrl(server) {
            var path = 'load.min.js';
            var urls = [];
            if (isArray(server)) {
                for (var i = 0, len = server.length; i < len; i++) {
                    urls.push(joinUrl(config.protocol, server[i], path));
                }
            } else {
                var url = joinUrl(config.protocol, server, path);
                urls = [url, url];
            }

            return urls;
        }
        var urls = genUrl(config.staticServer || ['cstaticdun.126.net', 'cstatic.dun.163yun.com']);

        function step(i) {
            var url = urls[i] + '?v=' + getTimestamp(CACHE_MIN);
            loadScript(url, function (err) {
                if (err || !window.initNECaptcha) { // loadjs的全局变量
                    i = i + 1;
                    if (i === urls.length) {
                        return cb(new Error('Failed to load script(' + url + ').' + (err ? err.message : 'unreliable script')));
                    }
                    return step(i);
                }
                return cb(null);
            });
        }
        step(0);
    }

    function initNECaptchaWithFallback(options, onload, onerror) {
        var captchaIns = null;

        var config = normalizeFallbackConfig(options);
        var defaultFallback = config.defaultFallback !== false;
        var langPkg = FALLBACK_LANG[config.lang === 'zh-CN' ? config.lang : 'en'];
        var storeKey = window.location.pathname + '_' + config.captchaId + '_NECAPTCHA_ERROR_COUNTS';
        try {
            errorCallbackCount = parseInt(localStorage.getItem(storeKey) || 0, 10);
        } catch (error) { }

        var fallbackFn = !defaultFallback ? config.onFallback || function () { } : function (validate) {
            function setFallbackTip(instance) {
                if (!instance) return;
                setFallbackTip(instance._captchaIns);
                if (!instance.$el) return;
                var tipEles = queryAllByClassName('.yidun-fallback__tip', instance.$el);
                if (!tipEles.length) return;

                // 确保在队列的最后
                setTimeout(function () {
                    for (var i = 0, l = tipEles.length; i < l; i++) {
                        setDomText(tipEles[i], langPkg);
                    }
                }, 0);
            }
            setFallbackTip(captchaIns);

            config.onVerify && config.onVerify(null, { validate: validate });
        };
        var noFallback = !defaultFallback && !config.onFallback;

        var proxyOnError = function (error) {
            errorCallbackCount++;
            if (errorCallbackCount < config.errorFallbackCount) {
                try {
                    localStorage.setItem(storeKey, errorCallbackCount);
                } catch (err) { }

                onerror(error);
            } else {
                fallbackFn(DEFAULT_VALIDATE);
                proxyRefresh();
                noFallback && onerror(error);
            }
        };

        var proxyRefresh = function () {
            errorCallbackCount = 0;
            try {
                localStorage.setItem(storeKey, 0);
            } catch (err) { }
        };

        config.onError = proxyOnError;
        config.onDidRefresh = proxyRefresh;

        loadResource(config, function (error) {
            if (error) {
                proxyOnError(error);
            } else {
                window.initNECaptcha(config, function (instance) {
                    captchaIns = instance;
                    onload && onload(instance);
                }, noFallback ? onerror : function (error) {
                    proxyOnError(error);
                });
            }
        });
    }

    return initNECaptchaWithFallback;
}));
